package org.wii.base.web;

import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.context.request.async.AsyncRequestTimeoutException;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.wii.base.bean.ResultBean;
import org.wii.base.exception.IWiiException;
import org.wii.base.exception.ParamException;
import org.wii.base.exception.constant.CommonExceptionExceptionEnum;
import org.wii.base.exception.constant.UserRequestBaseExceptionEnum;

import javax.servlet.http.HttpServletRequest;
import java.util.List;

/**
 * 全局异常控制类
 * @author tanghong on 2022/12/23
 */
@Slf4j
@ControllerAdvice
@Order(Integer.MAX_VALUE)
public class GlobalExceptionHandler {
    /**
     * 请求参数缺失异常
     */
    @ExceptionHandler(MissingServletRequestParameterException.class)
    @ResponseBody
    @ResponseStatus(value = HttpStatus.BAD_REQUEST)
    public ResultBean<String> missingParam(MissingServletRequestParameterException missingServletRequestParameterException) {
        String parameterName = missingServletRequestParameterException.getParameterName();
        String parameterType = missingServletRequestParameterException.getParameterType();
        return ResultBean.result(new ParamException(UserRequestBaseExceptionEnum.MISSING_REQUEST_PARAMETER_EXCEPTION, parameterName, parameterType));
    }

    /**
     * HttpMessageConverter转化异常，一般为json解析异常
     */
    @ExceptionHandler(HttpMessageNotReadableException.class)
    @ResponseBody
    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
    public ResultBean<String> httpMessageNotReadable(HttpMessageNotReadableException httpMessageNotReadableException) {
        log.error("参数格式传递异常，具体信息为：{}", httpMessageNotReadableException.getMessage());
        return ResultBean.result(new IWiiException(UserRequestBaseExceptionEnum.HTTP_MESSAGE_CONVERTER_ERROR));
    }

    /**
     * 拦截不支持媒体类型异常
     */
    @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
    @ResponseBody
    @ResponseStatus(value = HttpStatus.NOT_ACCEPTABLE)
    public ResultBean<String> httpMediaTypeNotSupport(HttpMediaTypeNotSupportedException httpMediaTypeNotSupportedException) {
        log.error("参数格式传递异常，具体信息为：{}", httpMediaTypeNotSupportedException.getMessage());
        return ResultBean.result(new IWiiException(UserRequestBaseExceptionEnum.HTTP_MEDIA_TYPE_NOT_SUPPORT));
    }

    /**
     * 不受支持的http method
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    @ResponseBody
    @ResponseStatus(value = HttpStatus.METHOD_NOT_ALLOWED)
    public ResultBean<String> methodNotSupport(HttpServletRequest request) {
        String httpMethod = request.getMethod().toUpperCase();
        return ResultBean.result(new IWiiException(UserRequestBaseExceptionEnum.HTTP_METHOD_NOT_SUPPORT, httpMethod));
    }

    /**
     * 404找不到资源
     */
    @ExceptionHandler(NoHandlerFoundException.class)
    @ResponseBody
    @ResponseStatus(value = HttpStatus.NOT_FOUND)
    public ResultBean<String> notFound(NoHandlerFoundException e) {
        return ResultBean.result(new IWiiException(UserRequestBaseExceptionEnum.NOT_FOUND));
    }

    /**
     * 请求参数校验失败，拦截 @Valid 校验失败的情况
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseBody
    @ResponseStatus(value = HttpStatus.OK)
    public ResultBean<String> methodArgumentNotValidException(MethodArgumentNotValidException e) {
        String bindingResult = getArgNotValidMessage(e.getBindingResult());
        return ResultBean.result(new IWiiException(UserRequestBaseExceptionEnum.VALIDATE_ARG_ERR, bindingResult));
    }

    /**
     * 请求参数校验失败，拦截 @Validated 校验失败的情况
     * <p>
     * 两个注解 @Valid 和 @Validated 区别是后者可以加分组校验，前者没有分组校验
     */
    @ExceptionHandler(BindException.class)
    @ResponseBody
    @ResponseStatus(value = HttpStatus.OK)
    public ResultBean<String> bindException(BindException e) {
        String bindingResult = getArgNotValidMessage(e.getBindingResult());
        return ResultBean.result(new IWiiException(UserRequestBaseExceptionEnum.VALIDATE_ARG_ERR, bindingResult));
    }


    /**
     * 拦截业务代码抛出的异常
     */
    @ExceptionHandler(IWiiException.class)
    @ResponseStatus(HttpStatus.OK)
    @ResponseBody
    public ResultBean<String> businessError(IWiiException e) {
        log.error("业务异常，具体信息为：{}", e.getMessage());
        return ResultBean.result(e);
    }

    /**
     * 线程主动终止异常
     * @param e
     * @return
     */
    @ExceptionHandler(AsyncRequestTimeoutException.class)
    @ResponseStatus(HttpStatus.OK)
    @ResponseBody
    public ResultBean<String> interruptedException(AsyncRequestTimeoutException e) {
        log.error("线程主动终止异常");
        return ResultBean.result(new IWiiException(CommonExceptionExceptionEnum.ACTIVE_DISCONNECT));
    }


    /**
     * 拦截未知的运行时异常
     */
    @ExceptionHandler(Throwable.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ResponseBody
    public ResultBean<String> serverError(Throwable e) {
        log.error("服务器运行异常", e);
        return ResultBean.result(new IWiiException(CommonExceptionExceptionEnum.UNKNOWN, e.getMessage()));
    }


    /**
     * 获取请求参数不正确的提示信息
     * <p>
     * 多个信息，拼接成用逗号分隔的形式
     */
    private String getArgNotValidMessage(BindingResult bindingResult) {
        if (bindingResult == null) {
            return "";
        }
        StringBuilder stringBuilder = new StringBuilder();

        //多个错误用逗号分隔
        List<ObjectError> allErrorInfos = bindingResult.getAllErrors();
        for (ObjectError error : allErrorInfos) {
            stringBuilder.append(",").append(error.getDefaultMessage());
        }

        //最终把首部的逗号去掉
        return StrUtil.removePrefix(stringBuilder.toString(), ",");
    }
}
